package table.node;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import table.Row;

public class FormulaNode extends BaseNode {
	
	public static FormulaNode formulaStringToNode(String formulaStr){
		FormulaNode rootNode = new FormulaNode("=");
		FormulaNode thisNode = rootNode;
		String tempStr = ("" + formulaStr).trim();
		for( FormulaNode nextNode = FormulaNode.getNode(tempStr); 
			  nextNode != null; 
			  tempStr = tempStr.trim().substring(thisNode.getFormulaStr().length()),
			  nextNode = FormulaNode.getNode(tempStr)){

			if(thisNode != null){
				float nextPriority = nextNode.getPriority();
				float thisPriority = thisNode.getPriority();
				if( nextPriority < thisPriority ){
					thisNode.addSubNode(nextNode);
				} else {
					FormulaNode insertPositonNode = null;
					for(insertPositonNode = thisNode; insertPositonNode.getParentNode() != null; insertPositonNode= insertPositonNode.getParentNode() ){
						if(insertPositonNode.getParentNode().getPriority() > nextNode.getPriority()){
							break;
						}
					}
					insertPositonNode.insertUpNode(nextNode);
				}
			} else {
				rootNode.addSubNode(nextNode);
			}
			if(nextNode.getFormulaStr().equals("(")){
				nextNode.setPriority(5);
			} else if (nextNode.getFormulaStr().equals(")")){
				nextNode.setPriority(1);
			}
			thisNode = nextNode;
		}
		return rootNode;
	}
	public static FormulaNode getNode(String formulaStr){
		/*
		 * operator node : "/^[\\+\\-\\*\\/\\(\\)]/"
		 * variable node : "/^\\[[^\\[\\]]+\\]/"
		 * constant node : "/^\\d+/"
		 * */
		formulaStr = formulaStr.trim();

		List<String> nodeRegexps = new ArrayList<String>();  
		nodeRegexps.add("^[\\+\\-\\*/\\(\\)]");
		nodeRegexps.add("^\\[[^\\[\\]]+\\]");
		nodeRegexps.add("^\\d+");
		
		for(int i=0; i < nodeRegexps.size(); i++){
			String nodeRegexp = nodeRegexps.get(i);
			Pattern myPattern = Pattern.compile(nodeRegexp);
			Matcher matcher = myPattern.matcher(formulaStr);
			if(matcher.find()){
				String nodeStr = formulaStr.substring(matcher.start(), matcher.end());
				return new FormulaNode(nodeStr);
			}
		}
		return null;
	}

	
	private FormulaNode parentNode;
	
	private String formulaStr;
	private String formulaType;
	private int priority;  
	
	public FormulaNode(){
		
	}

	public FormulaNode(String nodeStr){
		nodeStr = nodeStr.trim();
		String operatorRegexp = "^[\\+\\-\\*/\\(\\)\\=]$"; //operator node : //
		String varRegexp = "^\\[[^\\[\\]]+\\]$";
		String conRegexp = "^\\d+$";
		if(nodeStr.matches(operatorRegexp)){
			this.setFormulaStr(nodeStr);
			this.setFormulaType("operator");
			if(nodeStr.equals("+") || nodeStr.equals("-")){
				this.setPriority(4);
			} else if(nodeStr.equals("*") || nodeStr.equals("/")){
				this.setPriority(3);
			} else if( nodeStr.equals( "(" ) ){
				this.setPriority(2);
			} else if( nodeStr.equals( ")" ) ){
				this.setPriority(5);
			} else if( nodeStr.equals( "=" ) ){
				this.setPriority(999);
			}
		} else if(nodeStr.matches(varRegexp)){
			Pattern myPattern = Pattern.compile(varRegexp);
			Matcher matcher = myPattern.matcher(nodeStr);
			if(matcher.find()){
				this.setFormulaStr(nodeStr);
				this.setFormulaType("variable");
				this.setPriority(1);
			}
		} else if(nodeStr.matches(conRegexp)){
			Pattern myPattern = Pattern.compile(conRegexp);
			Matcher matcher = myPattern.matcher(nodeStr);
			if(matcher.find()){
				this.setFormulaStr(nodeStr);
				this.setFormulaType("constant");
				this.setPriority(1);
			}
		} else {
			
		}
	}
	
	public void addSubNode(FormulaNode subNode){
		this.getSubNodes().add(subNode);
		subNode.setParentNode(this);
	}
	public void removeSubNode(FormulaNode subNode){
		this.getSubNodes().remove(subNode);
		subNode.setParentNode(null);
	}
	public void insertUpNode(FormulaNode upNode){
		FormulaNode oldParentNode = this.getParentNode();
		if(oldParentNode !=null){
			oldParentNode.removeSubNode(this);
			oldParentNode.addSubNode(upNode);
		}
		upNode.addSubNode(this);
	}
	
	public String toString(Row dataRow){
		try {
			return String.valueOf(this.toDouble(dataRow));
		} catch(Exception e){
			
		}
		return "";
	}
	
	public Double toDouble(Row dataRow) throws Exception{
		List<DataNode> subNodes = this.getSubNodes();
		Double x = 0.0;
		Double y = 0.0;
		if(subNodes!= null && subNodes.size() > 0){
				x = ((FormulaNode) subNodes.get(0)).toDouble(dataRow);
			if(subNodes.size() > 1){
				y = ((FormulaNode) subNodes.get(1)).toDouble(dataRow);
			}
		}
		if(this.getFormulaType().equals("operator")){
			if(this.getFormulaStr().equals("+")){
				return x + y;
			} else if(this.getFormulaStr().equals("-")){
				return x - y;
			} else if(this.getFormulaStr().equals("*")){
				return x * y;
			} else if(this.getFormulaStr().equals("/")){
				return x / y;
			} else if( this.getFormulaStr().equals( "(" ) ){
				return x;
			} else if( this.getFormulaStr().equals( ")" ) ){
				return x;
			} else if( this.getFormulaStr().equals( "=" ) ){
				return x;
			}
		} else if(this.getFormulaType().equals("constant")){
			return Double.parseDouble(this.getFormulaStr());
		} else if(this.getFormulaType().equals("variable")){
			String nodeName = this.getFormulaStr().substring(1, this.getFormulaStr().length()-1);
			return Double.parseDouble(dataRow.getValueByName(nodeName).toString());
		} else {
			throw new Exception();
		}
		return null;
	}
	
	public void setParentNode(FormulaNode parentNode) {
		this.parentNode = parentNode;
	}
	public FormulaNode getParentNode() {
		return parentNode;
	}
	public void setFormulaStr(String formulaStr) {
		this.formulaStr = formulaStr;
	}
	public String getFormulaStr() {
		return formulaStr;
	}
	public void setFormulaType(String formulaType) {
		this.formulaType = formulaType;
	}
	public String getFormulaType() {
		return formulaType;
	}
	public void setPriority(int priority) {
		this.priority = priority;
	}
	public int getPriority() {
		return priority;
	}

}
